package GUIComponents.HlavniKomponentyComponents;

import Jama.EigenvalueDecomposition;
import Jama.Matrix;
import statistics.poznavaniTextu.Elipsa;
import statistics.poznavaniTextu.PointBasen;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

/*
*   File HlKompGraf 
*   in package GUIComponents.HlavniKomponentyComponents
*   was created by slaha
*   on 27.4.12 at 16:45.
*
*/
public class HlKompGraf extends JPanel implements MouseMotionListener {

  final short SPODNI_OKRAJ = 35;
  final short LEVY_OKRAJ = 55;
  //final short VNITRNI_OKRAJ = 10;
  private int w;
  private int h;
  double minY, maxY;
  double minX, maxX;

  double kresleneMinY, kresleneMaxY;
  double kresleneMinX, kresleneMaxX;

  int stredX, stredY;
  double pxX, pxY;

  HashMap<String, ArrayList<PointBasen>> autoriBody;
  HashMap<String, ArrayList<Rect2D>> autoriBodyPx;
  HashMap<String, Elipsa> autoriElipsy = new HashMap<>();
  HashMap<String, Color> autoriBarvy = new HashMap<>();
  int pocetBasni;
  double sumaDvouLambda, sumaVsechLambda;


  public HlKompGraf(HashMap<String, ArrayList<PointBasen>> autoriBody,
                    double sumaDvouLambda, double sumaVsechLambda) {
    addMouseMotionListener(this);


    this.autoriBody = autoriBody;
    this.pocetBasni = pocetBasni();
    this.sumaDvouLambda = sumaDvouLambda;
    this.sumaVsechLambda = sumaVsechLambda;

  }

  
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;

    g2.setStroke(new BasicStroke(1f));
    w = getWidth();
    h = getHeight();


    setBackground(Color.white);
    setForeground(Color.black);
    g2.setFont(new Font(g2.getFont().getFontName(), Font.PLAIN, 11));

    Rectangle2D r = new Rectangle2D.Double(LEVY_OKRAJ,
                                           SPODNI_OKRAJ,
                                           w - (2 * LEVY_OKRAJ),
                                           h - (2 * SPODNI_OKRAJ));
    g2.draw(r);

    nastavHranicniBody();
    nastavOsy(g2, r);

    vykresliPopiskyY(g2, r);
    vykresliPopiskyX(g2, r);
    this.autoriBodyPx = dejBody();
    vykresliBody(g2);
  }

  void vykresliBody(Graphics2D g) {
    g.translate(stredX, stredY);
    g.setStroke(new BasicStroke(2f));


    for(String autor : autoriBodyPx.keySet()) {
      //.. těžiště
      double sumaX = 0;
      double sumaY = 0;
      int pocet = autoriBodyPx.get(autor).size();

      //..matice autora
      double [][] m = dejMaticiAutora(autor);
      Matrix matrixAutora = new Matrix(m);
      Random numGen = new Random(System.currentTimeMillis());
      if (!autoriBarvy.containsKey(autor)){
        numGen.setSeed(System.nanoTime());
        autoriBarvy.put(autor, new Color(numGen.nextInt(200), numGen.nextInt(200), numGen.nextInt(200)));
      }

      g.setColor(autoriBarvy.get(autor));

      //..kreslím body a počítám těžiště
      for (Rect2D point : autoriBodyPx.get(autor)) {
        g.fill(point);
        sumaX += (point.x + 3);
        sumaY += (point.y + 3);
      }

      //..poloha těžiště
      double x = (sumaX / pocet);
      double y = (sumaY / pocet);


      if (pocet > 2) {
        Elipsa e = dejElipsu(matrixAutora, x, y);
        if (e != null) {
          autoriElipsy.put(autor, e);
          e.paint(g);
        }
      } else {
        g.draw(new Rectangle2D.Double(x-2, y-2, 4, 4));
        if (pocet == 2) {
          g.fillOval((int) Math.round(x - 2), (int) Math.round(y - 2), 4, 4);
        }
      }


    }

    g.translate(-stredX, -stredY);
    String vzorec =
        "q(2) = " + (
        + (double) (Math.round(sumaDvouLambda * 100) / 100d) + "/" +
        + (double) (Math.round(sumaVsechLambda * 100) / 100d) + " = " +
        + (double) (Math.round((sumaDvouLambda/sumaVsechLambda) * 100d) / 100d)
    );

    FontMetrics metrics = new FontMetrics(g.getFont()) { };
    Rectangle2D bounds = metrics.getStringBounds(vzorec, null);
    g.setColor(Color.black);
    g.setFont(new Font(g.getFont().getFontName(), Font.ITALIC, 11));
    g.drawString(vzorec, (int) (w - bounds.getWidth() - LEVY_OKRAJ-5), (int) (h - bounds.getHeight()- SPODNI_OKRAJ));
  }

  void nastavHranicniBody() {
    this.maxX = dejMaximumX();
    this.minX = dejMinimumX();
    this.maxY = dejMaximumY();
    this.minY = dejMinimumY();
    kresleneMaxX = Math.ceil(maxX) + 20;
    kresleneMinX = Math.floor(minX) - 20;
    kresleneMaxY = Math.ceil(maxY) + 20;
    kresleneMinY = Math.floor(minY) - 20;
  }

  private void vykresliPopiskyX(Graphics2D g, Rectangle2D r) {
    int y = (int) r.getMaxY() + 30;
    FontMetrics metrics = new FontMetrics(new Font("Verdana", Font.PLAIN, 10)) {};

    double prirustek;

    if (pxX > 130) {
      prirustek = 0.5;
    } else if (pxX < 25) {
      prirustek = 10;
    } else {
      prirustek = 1;
    }

    double lokalMin;

    if (kresleneMaxX > 0) {
      lokalMin = 0;

      for (double i = 0; lokalMin <= kresleneMaxX; lokalMin += prirustek) {
        String label = Double.toString(lokalMin);

        int x = (int) Math.round(stredX + (i * pxX));

        i += prirustek;

        Rectangle2D bounds = metrics.getStringBounds(label, null);
        int widthInPixels = (int) bounds.getWidth();

        g.drawLine(x, (int) r.getMaxY(), x, (int) r.getMaxY() + 2);

        g.drawString(label, (int) (x - (0.5 * widthInPixels)), y);

      }
    }

    if (kresleneMinX < 0) {

      lokalMin = 0;
      for (double i = 0; lokalMin >= kresleneMinX; lokalMin -= prirustek) {
      String label = Double.toString(lokalMin);

      int x = (int) Math.round(stredX - (i * pxX));

      i += prirustek;

      Rectangle2D bounds = metrics.getStringBounds(label, null);
      int widthInPixels = (int) bounds.getWidth();

      g.drawLine(x, (int) r.getMaxY(), x, (int) r.getMaxY() + 2);

      g.drawString(label, (int) (x - (0.5 * widthInPixels)), y);

      }
    }
  }

  private void vykresliPopiskyY(Graphics2D g, Rectangle2D r) {
    int x = LEVY_OKRAJ - 50;

    FontMetrics metrics = new FontMetrics(new Font("Verdana", Font.PLAIN, 10)) {    };

    double prirustek;

    if (pxY > 40) {
      prirustek = 0.5;
    } else if (pxY > 20) {
      prirustek = 2;
    } else if (pxY < 10) {
      prirustek = 10;
    } else {
      prirustek = 1;
    }

    double lokalMin;
    if (kresleneMaxY > 0) {
      lokalMin = 0;
      for (double i = 0; lokalMin <= kresleneMaxY; lokalMin += prirustek) {
        String label = Double.toString(lokalMin);

        int y = (int) Math.round(stredY - (i * pxY));

        i += prirustek;

        Rectangle2D bounds = metrics.getStringBounds(label, null);
        int heightInPixels = (int) bounds.getHeight();

        g.drawLine(LEVY_OKRAJ - 2, y, LEVY_OKRAJ + 2, y);

        g.drawString(label, x, (int) (y + (0.5 * heightInPixels)));
      }
    }

    if (kresleneMinY < 0) {
      lokalMin = 0;
      for (double i = 0; lokalMin >= kresleneMinY; lokalMin -= prirustek) {
        String label = Double.toString(lokalMin);

        int y = (int) Math.round(stredY + (i * pxY));

        i += prirustek;

        Rectangle2D bounds = metrics.getStringBounds(label, null);
        int heightInPixels = (int) bounds.getHeight();

        g.drawLine(LEVY_OKRAJ - 2, y, LEVY_OKRAJ + 2, y);

        g.drawString(label, x, (int) (y + (0.5 * heightInPixels)));

      }
    }
  }

  private double dejMinimumX() {
    double m = Double.MAX_VALUE;

    for (String autor : this.autoriBody.keySet()) {
      for (PointBasen bod : autoriBody.get(autor)) {
        if (bod.x < m) {
          m = bod.x;
        }
      }
    }

    return m;
  }

  private double dejMaximumX() {
    double m = Double.MIN_VALUE;

    for (String autor : this.autoriBody.keySet()) {
      for (PointBasen bod : autoriBody.get(autor)) {
        if (bod.x > m) {
          m = bod.x;
        }
      }
    }

    return m;
  }

  private double dejMinimumY() {
    double m = Double.MAX_VALUE;

    for (String autor : this.autoriBody.keySet()) {
      for (PointBasen bod : autoriBody.get(autor)) {
        if (bod.y < m) {
          m = bod.y;
        }
      }
    }

    return m;
  }

  private double dejMaximumY() {
    double m = Double.MIN_VALUE;

    for (String autor : this.autoriBody.keySet()) {
      for (PointBasen bod : autoriBody.get(autor)) {
        if (bod.y > m) {
          m = bod.y;
        }
      }
    }

    return m;
  }

  int pocetBasni() {
    int pocet = 0;
    for(String autor : autoriBody.keySet()) {
      pocet += autoriBody.get(autor).size();
    }

    return pocet;
  }

  void nastavOsy(Graphics2D g, Rectangle2D r) {

    if (kresleneMinX > 0) {
      this.pxX = ((r.getWidth())) / (Math.abs(kresleneMaxX));
      stredX = (int) Math.round(r.getMinX() + pxX);

    } else if (kresleneMaxX < 0) {
      this.pxX = ((r.getWidth())) / (Math.abs(kresleneMinX));
      stredX = (int) Math.round(r.getMaxX());
    } else {
      this.pxX = ((r.getWidth())) / (Math.abs(kresleneMaxX) + Math.abs(kresleneMinX));
      stredX = (int) Math.round(r.getMinX() + (pxX * Math.abs(kresleneMinX)));
    }

    if (kresleneMinY > 0) {
      this.pxY = ((r.getHeight())) / (Math.abs(kresleneMaxY));
      stredY = (int) Math.round(r.getMinY() + (pxY * kresleneMaxY));
    } else if (kresleneMaxY < 0) {
      this.pxY = ((r.getHeight())) / (Math.abs(kresleneMinY));
      stredY = (int) Math.round(r.getMaxY());
    } else {
      this.pxY = ((r.getHeight())) / (Math.abs(kresleneMaxY) + Math.abs(kresleneMinY));
      stredY = (int) Math.round(r.getMinY() + (pxY * Math.abs(kresleneMaxY)));
    }

    g.drawLine((int) r.getMinX(), stredY, (int) r.getMaxX(), stredY);
    g.drawLine(stredX, (int) r.getMinY(), stredX, (int) r.getMaxY());


  }

  private HashMap<String, ArrayList<Rect2D>> dejBody() {
    
    HashMap<String, ArrayList<Rect2D>> hm  = new HashMap<>();

    for (String autor : autoriBody.keySet()) {
      for (PointBasen bod : autoriBody.get(autor)) {
        double x = (bod.x * pxX);
        double absY = (bod.y) * pxY * (-1);
        double y = (absY);

        if (hm.containsKey(autor)) {
          hm.get(autor).add(new Rect2D(x-3, y-3,6,6, bod.getJmenoBasne(), autor));
        } else {
          ArrayList<Rect2D> al = new ArrayList<>();
          al.add(new Rect2D(x-3, y-3,6,6, bod.getJmenoBasne(), autor));
          hm.put(autor, al);
        }
      }
    }

    return hm;
  }

  double[][] dejMaticiAutora(String autor) {
    double[][] m = new double[autoriBodyPx.get(autor).size()][2];
    int i = 0;
    for (Rect2D point : autoriBodyPx.get(autor)) {
      m[i][0] = point.x +3;

      m[i][1] = point.y +3;

      i++;
    }

    return m;
  }

  Elipsa dejElipsu(Matrix matrix, double x, double y) {
    double[][] kovariancniMatice = dejKovariancniMatici(matrix);
    Matrix inverzni;
    try {
     inverzni = new Matrix(kovariancniMatice).inverse();
    } catch (Exception ex) {
      inverzni = null;
    }

    if (inverzni != null) {
      EigenvalueDecomposition eig = inverzni.eig();
      double[] vlCisla = eig.getRealEigenvalues();

      /*double a = Math.sqrt(1d / Math.abs((vlCisla[0] > vlCisla[1] ? vlCisla[0] : vlCisla[1])));
      double b = Math.sqrt(1d / Math.abs((vlCisla[0] < vlCisla[1] ? vlCisla[0] : vlCisla[1])));*/
      double a = Math.sqrt(1d / Math.abs((vlCisla[0] > vlCisla[1] ? vlCisla[0] : vlCisla[0])));
      double b = Math.sqrt(1d / Math.abs((vlCisla[0] < vlCisla[1] ? vlCisla[1] : vlCisla[1])));

      double[] vlV1 = (vlCisla[0] > vlCisla[1]) ? eig.getV().getMatrix(0, 1, 0, 0).getColumnPackedCopy() : eig.getV().getMatrix(0, 1, 1, 1).getColumnPackedCopy();
      double[] vlV2 = (vlCisla[0] < vlCisla[1]) ? eig.getV().getMatrix(0, 1, 0, 0).getColumnPackedCopy() : eig.getV().getMatrix(0, 1, 1, 1).getColumnPackedCopy();

      double[][] vl = new double[2][2];
      vl[0][0] = vlV1[0];
      vl[0][1] = vlV1[1];
      vl[1][0] = vlV2[0];
      vl[1][1] = vlV2[1];
      double[][] vlV = normuj(vl);

      //return new Elipsa(x, y, a*2.4477, b*2.4477, eig.getV().getArray(), pxX , pxY);
      return new Elipsa(x, y, a, b, eig.getV().getArray(), pxX, pxY);
    }
    else {
      return null;
    }
  }

  private double[][] dejKovariancniMatici(Matrix m) {
    double[][] kM = new double[2][2];

    kM[0][0] = kovariance(0, 0, m);
    kM[0][1] = kovariance(0, 1, m);
    kM[1][0] = kM[0][1];
    kM[1][1] = kovariance(1, 1, m);

    return kM;
  }

  private double kovariance(int jevX, int jevY, Matrix m) {
    double kovariance = 0d;
    double n_1 = (double) m.getRowDimension() -1;
    double prumerX = prumer(m, jevX);
    double prumerY = prumer(m, jevY);

    for (int i = 0; i < m.getRowDimension(); i++) {
      double x = m.get(i, jevX);
      double y = m.get(i, jevY);

      double clen1 = x - prumerX;
      double clen2 = y - prumerY;
      kovariance += (clen1 * clen2) / n_1;
    }

    return (kovariance);
  }

  double prumer(Matrix m, int sloupec) {
    double prumer = 0d;

    for (double d : m.getMatrix(0, m.getRowDimension()-1, sloupec, sloupec).getRowPackedCopy()) {
      prumer += d;
    }

    return prumer / m.getRowDimension();
  }

  double[][] normuj(double[][] p) {
    double[][] norm = new double[2][2];
    double suma1 = Math.pow(p[0][0], 2) + Math.pow(p[0][1], 2);
    double suma2 = Math.pow(p[1][0], 2) + Math.pow(p[1][1], 2);

    norm[0][0] = p[0][0] / suma1;
    norm[0][1] = p[0][1] / suma1;
    norm[1][0] = p[1][0] / suma2;
    norm[1][1] = p[1][1] / suma2;


    return norm;
  }
  
  public static void main(String[] args) {



    HashMap<String, ArrayList<PointBasen>> hm = new HashMap<>();

    ArrayList<PointBasen> al = new ArrayList<>();


  /*  al.add(new PointBasen(1.2, 0.9, "Basen"));
    al.add(new PointBasen(2.5, 3.9, "Basen"));
    al.add(new PointBasen(0.7, 0.4, "Basen"));
    al.add(new PointBasen(4.2, 5.8, "Basen"));

    al.add(new PointBasen(0.75, 1, "Basen"));
    al.add(new PointBasen(2.5, 2, "Basen"));
    al.add(new PointBasen(3.7, 3, "Basen"));
    al.add(new PointBasen(4, 4, "Basen"));*/

     for (int i = 0; i < 10; i++) {
      al.add(new PointBasen(i + 5 * Math.random() - 5 * Math.random(), i - 3 * Math.random() + 3 * Math.random(), "Basen"));
    }
       al.add(new PointBasen(0, 0, "Basen"));



/*

    al.add(new PointBasen(8.263963548068703e+001, 1.140848688912681e+000, "Basen"));
      al.add(new PointBasen(4.269458950098058e+001, -1.081653047684519e+001, "Basen"));
      al.add(new PointBasen(7.784018874360959e+001, -4.784311911052805e+000, "Basen"));
      al.add(new PointBasen(3.147464203366877e+001, -1.863658624675532e+000, "Basen"));
      al.add(new PointBasen(1.762231037486428e+001, -1.513711098353480e+000, "Basen"));
      al.add(new PointBasen(4.319842617871072e+001, -1.876069201375512e+001, "Basen"));
      al.add(new PointBasen(3.560903044411714e+001, -1.875091256890208e+001, "Basen"));
      al.add(new PointBasen(3.311976636917987e+001, -8.689081082795555e+000, "Basen"));
      al.add(new PointBasen(6.603272094952051e+001, -1.319057828023813e+001, "Basen"));
      al.add(new PointBasen(1.753760528550648e+001, -1.519588409471362e-001, "Basen"));
    */


        hm.put("Autor", al);



        JFrame jf = new JFrame();
        jf.setSize(600, 500);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.add(new HlKompGraf(hm, 123.4948943445584d, 5.234d));
        jf.setVisible(true);

      }

  @Override
  public void mouseDragged(MouseEvent e) {
    //To change body of implemented methods use File | Settings | File Templates.
  }

  /**
   * Invoked when the mouse cursor has been moved onto a component
   * but no buttons have been pushed.
   */
  @Override
  public void mouseMoved(MouseEvent e) {

    for(String autor:autoriBodyPx.keySet()){
      for (Rect2D r : autoriBodyPx.get(autor)) {



        double xx = e.getX()-stredX;
        double yy = e.getY()-stredY;


        Point p = new Point((int) Math.round(xx), (int) Math.round(yy));


       if (r.contains(p)) {

          setToolTipText(r.autor + " - " +r.jmenoBasne);

        } else {
          
        }

      }
    }
    ToolTipManager.sharedInstance().mouseMoved(e);
  }
}

